/*
Description: awf waveform file decoder
Version	: v0.1
Author	: oujunxi
Date	: 2015/07/10
*/

#include "disp_waveform.h"

#define DEBUG_WAVEFILE
#ifdef DEBUG_WAVEFILE
#define WF_DBG(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_INFO(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_WRN(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_ERR(msg, fmt...)		printk(KERN_ERR msg, ##fmt)
#else
#define WF_DBG(msg, fmt...)
#define WF_INFO(msg, fmt...)
#define WF_WRN(msg, fmt...)		printk(KERN_WARNING msg, ##fmt)
#define WF_ERR(msg, fmt...)		printk(KERN_ERR msg, ##fmt)
#endif


#define    C_HEADER_INFO_OFFSET               		0
#define    C_HEADER_TYPE_ID_OFFSET            		0         	//eink type(eink id)
#define    C_HEADER_VERSION_STR_OFFSET        	1
#define    C_HEADER_INFO_SIZE                 			128		//size of awf file header
#define    C_TEMP_TBL_OFFSET                  			(C_HEADER_INFO_OFFSET+C_HEADER_INFO_SIZE)	//temperature table offset from the beginning of the awf file
#define    C_TEMP_TBL_SIZE                    			32     	// temperature table size

#define    C_MODE_ADDR_TBL_OFFSET             		(C_TEMP_TBL_OFFSET+C_TEMP_TBL_SIZE)             		//mode address table offset
#define    C_MODE_ADDR_TBL_SIZE               		64

#define    C_INIT_MODE_ADDR_OFFSET            		C_MODE_ADDR_TBL_OFFSET
#define    C_GC16_MODE_ADDR_OFFSET            	(C_MODE_ADDR_TBL_OFFSET+4)
#define    C_GC4_MODE_ADDR_OFFSET             		(C_MODE_ADDR_TBL_OFFSET+8)
#define    C_DU_MODE_ADDR_OFFSET              		(C_MODE_ADDR_TBL_OFFSET+12)
#define    C_A2_MODE_ADDR_OFFSET              		(C_MODE_ADDR_TBL_OFFSET+16)
#define    C_GC16_LOCAL_MODE_ADDR_OFFSET    	(C_MODE_ADDR_TBL_OFFSET+20)
#define    C_GC4_LOCAL_MODE_ADDR_OFFSET   	(C_MODE_ADDR_TBL_OFFSET+24)
#define    C_A2_IN_MODE_ADDR_OFFSET 			(C_MODE_ADDR_TBL_OFFSET+28)
#define    C_A2_OUT_MODE_ADDR_OFFSET 		(C_MODE_ADDR_TBL_OFFSET+32)

#define    C_INIT_MODE_OFFSET                 			(C_MODE_ADDR_TBL_OFFSET+C_MODE_ADDR_TBL_SIZE)

#define    C_REAL_TEMP_AREA_NUM               		15              //max temperature range number
#define    WF_MAX_COL                         				256		// GC16, 16*16 = 256


typedef struct {
	u8 load_flag;			//when awf has been loaded, init_flag = 1
	char* p_wf_vaddr;			//virtual address of waveform file
	u32 p_wf_paddr;			//phy address of waveform file
	EINK_PANEL_TYPE eink_panel_type;		//eink type, example PVI, OED and etc
	u8 wf_temp_area_tbl[C_TEMP_TBL_SIZE];	//temperature table

	/*phy address*/
	u32 p_init_wf;        		//init mode address (include temperature table address)
	u32 p_gc16_wf;           	//gc16 mode address (include temperature table address)
	u32 p_gc4_wf;            	//gc4 mode address (include temperature table address)
	u32 p_du_wf;             		//du mode address (include temperature table address)
	u32 p_A2_wf;             		//A2 mode address (include temperature table address)
	u32 p_gc16_local_wf;	//gc16 local mode address (include temperature table address)
	u32 p_gc4_local_wf;      	//gc4 local mode address (include temperature table address)
	u32 p_A2_in_wf;              	//A2 in mode address (include temperature table address)
	u32 p_A2_out_wf;              	//A2 out mode address (include temperature table address)
}AWF_WAVEFILE;

static AWF_WAVEFILE g_waveform_file;

/*
Description	: get temperature range index from temperature table, if temperature match
			  TBL[id] <= temperature < TBL[id + 1] condition, then temp range index = id
Input		: temperature -- temperature of eink panel
Output		: None
Return		: 0 & positive -- temperature range index, negative -- fail
*/
static __s32 get_temp_range_index(int temperature)
{
	__s32 index = -EINVAL;

	for(index = 0; index < C_TEMP_TBL_SIZE; index++) {
		if((g_waveform_file.wf_temp_area_tbl[index] == 0) && (index>0)) {
			break;
		}

		if(temperature < g_waveform_file.wf_temp_area_tbl[index]) {
			break;
		}
	}

	if(index > 0) {
		index -= 1;
	}

	return index;
}

/*
Description	: get mode address according to flush mode
Input		: mode -- flush mode
Output		: None
Return		: NULL -- get mode address fail, others -- get mode address successfully
*/
static u8 *get_mode_virt_address(enum eink_update_mode mode)
{
	u8 *p_wf_file = NULL;
	u32 offset = 0;

	switch (mode)
	{
		case EINK_INIT_MODE:
		{
			offset = (u32)g_waveform_file.p_init_wf - g_waveform_file.p_wf_paddr;
			p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
			break;
		}

		case EINK_DU_MODE:
		case EINK_DU_RECT_MODE:
		{
			offset = (u32)g_waveform_file.p_du_wf - g_waveform_file.p_wf_paddr;
			p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
			break;
		}

		case EINK_GC16_MODE:
		case EINK_GC16_RECT_MODE:
		{
			offset = (u32)g_waveform_file.p_gc16_wf - g_waveform_file.p_wf_paddr;
			p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
			break;
		}

		case EINK_A2_MODE:
		case EINK_A2_RECT_MODE:
		{
			offset = (u32)g_waveform_file.p_A2_wf - g_waveform_file.p_wf_paddr;
			p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
			break;
		}

		case EINK_GC16_LOCAL_MODE:
		case EINK_GC16_LOCAL_RECT_MODE:
		{
			offset = (u32)g_waveform_file.p_gc16_local_wf - g_waveform_file.p_wf_paddr;
			p_wf_file = (u8 *)g_waveform_file.p_wf_vaddr + offset;
			break;
		}

		default:
		{
			WF_WRN("unkown mode(0x%x)\n", mode);
			p_wf_file = NULL;
			break;
		}
	}

	return p_wf_file;
}

/*
Description	: get mode address according to flush mode
Input		: mode -- flush mode
Output		: None
Return		: NULL -- get mode address fail, others -- get mode address successfully
*/
static u32 get_mode_phy_address(enum eink_update_mode mode)
{
	u32 phy_addr = 0;

	switch (mode)
	{
		case EINK_INIT_MODE:
		{
			phy_addr = g_waveform_file.p_init_wf;
			break;
		}

		case EINK_DU_MODE:
		case EINK_DU_RECT_MODE:
		{
			phy_addr = g_waveform_file.p_du_wf;
			break;
		}

		case EINK_GC16_MODE:
		case EINK_GC16_RECT_MODE:
		{
			phy_addr = g_waveform_file.p_gc16_wf;
			break;
		}

		case EINK_A2_MODE:
		case EINK_A2_RECT_MODE:
		{
			phy_addr = g_waveform_file.p_A2_wf;
			break;
		}

		case EINK_GC16_LOCAL_MODE:
		case EINK_GC16_LOCAL_RECT_MODE:
		{
			phy_addr = g_waveform_file.p_gc16_local_wf;
			break;
		}

		default:
		{
			WF_WRN("unkown mode(0x%x)\n", mode);
			phy_addr = 0;
			break;
		}
	}

	return phy_addr;
}


/*
Description	: load waveform file to memory, and save each flush mode of address. This function
			  should be called before waveform data has been used.
Input		: path -- path of waveform file
Output		: None
Return		: 0 -- load waveform ok, others -- fail
*/
extern void *disp_malloc(u32 num_bytes, void *phy_addr);
__s32 init_waveform(const char *path)
{
	struct file *fp = NULL;
	__s32 file_len = 0;
	__s32 read_len = 0;
	mm_segment_t fs;
	loff_t pos;
	__u32 wf_buffer_len = 0;           //the len of waveform file
	u32 *pAddr = NULL;
	__s32 ret = -EINVAL;

	if (NULL == path) {
		WF_ERR("path is null\n");
		return -EINVAL;
	}

	WF_DBG("starting to load awf waveform file(%s)\n", path);
	fp = filp_open(path, O_RDONLY, 0);
	if(IS_ERR(fp))	{
		WF_ERR("fail to open waveform file(%s)", path);
		return -EBADF;
	}

	memset(&g_waveform_file, 0, sizeof(g_waveform_file));
	fs = get_fs();
	set_fs(KERNEL_DS);
	pos = 0;
	file_len = fp->f_dentry->d_inode->i_size;
	wf_buffer_len = file_len+1023;

	g_waveform_file.p_wf_vaddr= (char *)disp_malloc(wf_buffer_len, (u32 *)(&g_waveform_file.p_wf_paddr));
	if(NULL == g_waveform_file.p_wf_vaddr) {
		WF_ERR("fail to alloc memory for waveform file\n");
		ret = -ENOMEM;
		goto error;
	}

	read_len = vfs_read(fp, (char *)g_waveform_file.p_wf_vaddr, file_len, &pos);
	if(read_len != file_len) {
		WF_ERR("maybe miss some data(read=%d byte, file=%d byte) when reading waveform file\n", read_len, file_len);
		ret = -EAGAIN;
		goto error;
	}

	g_waveform_file.eink_panel_type = *((u8 *)g_waveform_file.p_wf_vaddr);
	WF_DBG("eink type=0x%x\n", g_waveform_file.eink_panel_type);

	//starting to load data
	memcpy(g_waveform_file.wf_temp_area_tbl,(g_waveform_file.p_wf_vaddr+C_TEMP_TBL_OFFSET),C_TEMP_TBL_SIZE);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_INIT_MODE_ADDR_OFFSET);
	g_waveform_file.p_init_wf = (u32)(g_waveform_file.p_wf_paddr+ *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC16_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc16_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC4_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc4_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_DU_MODE_ADDR_OFFSET);
	g_waveform_file.p_du_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC16_LOCAL_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc16_local_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_GC4_LOCAL_MODE_ADDR_OFFSET);
	g_waveform_file.p_gc4_local_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_IN_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_in_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	pAddr = (u32 *)(g_waveform_file.p_wf_vaddr + C_A2_OUT_MODE_ADDR_OFFSET);
	g_waveform_file.p_A2_out_wf = (u32)(g_waveform_file.p_wf_paddr + *pAddr);

	if(fp) {
		filp_close(fp, NULL);
		set_fs(fs);
	}

	g_waveform_file.load_flag = 1;

	WF_DBG("load waveform file(%s) successfully\n", path);
	return 0;

error:
	if(NULL != g_waveform_file.p_wf_vaddr)   {
		vfree(g_waveform_file.p_wf_vaddr);
		g_waveform_file.p_wf_vaddr = NULL;
	}

	if(fp) {
		filp_close(fp, NULL);
		set_fs(fs);
	}

	return ret;
}

/*
Description: get eink panel type, for example, 3 stands for ED060XC3(4-bit), 4 stands for ED060XD4(5-bit)
Input: None
Output: type -- save the type of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_eink_panel_type(EINK_PANEL_TYPE *type)
{
	if (1 != g_waveform_file.load_flag) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if (NULL == type) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	*type = g_waveform_file.eink_panel_type;
	return 0;
}

/*
Description: get bit number of eink panel, 4-bit stands for 16 grayscale, 5-bit stands for 32 grayscale
Input: None
Output: bit_num -- save the bit number of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_eink_panel_bit_num(enum  eink_bit_num *bit_num)
{
	if (1 != g_waveform_file.load_flag) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if (NULL == bit_num) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	if (ED060XD4 == g_waveform_file.eink_panel_type) {
		*bit_num = EINK_BIT_5;
	} else {
		*bit_num = EINK_BIT_4;
	}

	return 0;
}


/*
Description: get waveform data address according to mode and temperature
Input: None
Output: type -- save the type of eink panel
Return: 0 -- get eink panel type successfully, others -- fail
*/
int get_waveform_data(enum eink_update_mode mode, u32 temp, u32 *total_frames, u32 *wf_buf)
{
	u32 p_mode_phy_addr = 0;
	u8 *p_mode_virt_addr = NULL;
	u32 mode_temp_offset = 0;
	u8 *p_mode_temp_addr = NULL;
	//u16 *p_pointer = NULL;
	u32 temp_range_id = 0;

	if (1 != g_waveform_file.load_flag) {
		WF_ERR("waveform hasn't init yet, pls init first\n");
		return -EAGAIN;
	}

	if ((NULL == total_frames) || (NULL == wf_buf)) {
		WF_ERR("input param is null\n");
		return -EINVAL;
	}

	p_mode_virt_addr = get_mode_virt_address(mode);
	if (NULL == p_mode_virt_addr) {
		WF_ERR("get mode virturl address fail, mode=0x%x\n", mode);
		return -EINVAL;
	}

	p_mode_phy_addr = get_mode_phy_address(mode);
	if (0 == p_mode_phy_addr) {
		WF_ERR("get mode phy address fail, mode=0x%x\n", mode);
		return -EINVAL;
	}

	temp_range_id = get_temp_range_index(temp);
	if (temp_range_id < 0) {
		WF_ERR("get temp range index fail, temp=0x%x\n", temp);
		return -EINVAL;
	}

	mode_temp_offset =  *((u32 *)p_mode_virt_addr + temp_range_id);
	p_mode_temp_addr = (u8 *)p_mode_virt_addr + mode_temp_offset;

	*total_frames = *((u16 *)p_mode_temp_addr);
	mode_temp_offset = mode_temp_offset + 4;		//skip total frame(2 Byte) and dividor(2 Byte)

	*wf_buf = p_mode_phy_addr + mode_temp_offset;

	return 0;
}


/*
Description 	: free memory that used by waveform file, after that, waveform data cannot
			  be used until init_waveform function has been called. This function should
			  be called when unload module.
Input		: None
Output		: None
Return		: None
*/
void free_waveform(void)
{
	if(NULL != g_waveform_file.p_wf_vaddr)   {
		vfree(g_waveform_file.p_wf_vaddr);
		g_waveform_file.p_wf_vaddr = NULL;
	}

	g_waveform_file.load_flag = 0;
	return;
}


